#include "common\header.h"
#include "common\console.h"

struct text_buffer
{
	xgc_uint16	ref;
	xgc_uint16	w, h; // buffer size
	PCHAR_INFO	buffer; // buffer;
};

struct text_window
{
	xgc_uint16 x, y; // position of global window
	xgc_uint16 cols, rows; // window columns and rows
	xgc_uint16 sx, sy; // current scroll position
	xgc_uint16 cursor; // cursor position

	PCHAR_INFO border;
	PCHAR_INFO title;

	text_t text;
};

struct console_global
{
	xgc_word current;
	xgc_word blank;
	xgc_word border[text_count*2];
};

#ifndef _UNICODE
#define TCHAR_INFO( C )	(C.Char.AsciiChar)
#else
#define TCHAR_INFO( C )	(C.Char.UnicodeChar)
#endif

static console_global g = 
{ 
	FOREGROUND_BLUE | FOREGROUND_GREEN, 
	FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
	{ _T('-'),7,_T('|'),7,_T('|'),7,_T('-'),7,_T('.'),7,_T('.'),7,_T('.'),7,_T('.'),7, },
};

static text_window _text_window[255];
static text_buffer _text_buffer[255];

static xgc_handle _active_screen_buffer = xgc_nullptr;

xgc_bool initialize_console_library()
{
	memset( _text_window, 0, sizeof(_text_window) );
	_active_screen_buffer = GetStdHandle( STD_OUTPUT_HANDLE );
	for( xgc_size i = 0; i < _countof(_text_window); ++i )
	{
		_text_window[i].text = INVALID_BUFFER_INDEX;
	}

	return true;
}

xgc_void finialize_console_library()
{
	for( xgc_size i = 0; i < _countof(_text_buffer); ++i )
	{
		free( _text_buffer[i].buffer );
		_text_buffer[i].buffer = xgc_nullptr;
	}
}

xgc_void gotoxy( xgc_int16 x, xgc_int16 y )
{
	COORD cursor_position = { x, y };
	SetConsoleCursorPosition( _active_screen_buffer, cursor_position );
}

xgc_void gotoxy( window_t window, xgc_int16 x, xgc_int16 y )
{
	text_window& w = _text_window[window];
	w.cursor = y * w.cols + x;

	COORD cursor_position = { x + w.x, y + w.y };
	SetConsoleCursorPosition( _active_screen_buffer, cursor_position );
}

text_t text( xgc_uint16 w, xgc_uint16 h )
{
	text_t idx = 0;
	while( idx < _countof(_text_buffer) && _text_buffer[idx].buffer ) ++idx;

	_text_buffer[idx].buffer = (PCHAR_INFO)malloc( sizeof(CHAR_INFO) * w * h );
	memset( _text_buffer[idx].buffer, 0, sizeof(CHAR_INFO) * w * h );

	_text_buffer[idx].ref = 0;
	_text_buffer[idx].w = w;
	_text_buffer[idx].h = h;

	for( int i = 0; i < w * h; ++i )
	{
		_text_buffer[idx].buffer[i].Attributes = g.blank;
		_text_buffer[idx].buffer[i].Char.AsciiChar = ' ';
	}

	return idx;
}

window_t window( xgc_int16 x, xgc_int16 y, xgc_int16 cols, xgc_int16 rows, text_t text  )
{
	if( text == INVALID_BUFFER_INDEX )
		return INVALID_WINDOW_INDEX;

	text_buffer& t = _text_buffer[text];

	window_t idx = 0;
	while( idx < _countof(_text_window) && _text_window[idx].text != INVALID_BUFFER_INDEX ) ++idx;

	_text_window[idx].x = x;
	_text_window[idx].y = y;
	_text_window[idx].cols = cols?cols-1:t.w-1;
	_text_window[idx].rows = rows?rows-1:t.h-1;
	_text_window[idx].sx = 0;
	_text_window[idx].sy = 0;
	_text_window[idx].cursor = 0;

	_text_window[idx].border = (PCHAR_INFO)g.border;
	_text_window[idx].title = xgc_nullptr;

	_text_window[idx].text = text;

	++_text_buffer[text].ref;
	return idx;
}

xgc_void free( window_t window )
{
	text_window& w = _text_window[window];

	if( w.text != INVALID_BUFFER_INDEX )
	{
		text_buffer &t = _text_buffer[w.text];
		if( --t.ref == 0 )
		{
			free( t.buffer );
			t.buffer = xgc_nullptr;
		}
	}
}

static xgc_void scroll_buffer( text_buffer& w, xgc_uint16 row )
{
	if( row == 0 )
		return;

	for( int i = 0; i < w.w * w.h; ++i )
	{
		if( i + row * w.w >= w.w * w.h )
		{
			w.buffer[i].Attributes = g.blank;
			w.buffer[i].Char.AsciiChar = ' ';
		}
		else
		{
			w.buffer[i] = w.buffer[i + row*w.w];
		}
	}
}

xgc_void draw_window( window_t window )
{
	text_window& w = _text_window[window];
	DWORD write = 0;

	for( int i = 1; i < w.cols; ++i )
	{
		COORD posTop = { i + w.x, w.y };

		WriteConsoleOutputAttribute( _active_screen_buffer, &w.border[text_top].Attributes, 1, posTop, &write );
		WriteConsoleOutputCharacter( _active_screen_buffer, &TCHAR_INFO(w.border[text_top]), 1, posTop, &write );

		COORD posBottom = { i + w.x, w.y + w.rows };

		WriteConsoleOutputAttribute( _active_screen_buffer, &w.border[text_bottom].Attributes, 1, posBottom, &write );
		WriteConsoleOutputCharacter( _active_screen_buffer, &TCHAR_INFO(w.border[text_bottom]), 1, posBottom, &write );
	}

	for( int i = 1; i < w.rows; ++i )
	{
		COORD posLeft = { w.x, i + w.y };

		WriteConsoleOutputAttribute( _active_screen_buffer, &w.border[text_left].Attributes, 1, posLeft, &write );
		WriteConsoleOutputCharacter( _active_screen_buffer, &TCHAR_INFO(w.border[text_left]), 1, posLeft, &write );

		COORD posRight = { w.x + w.cols, i + w.y };

		WriteConsoleOutputAttribute( _active_screen_buffer, &w.border[text_right].Attributes, 1, posRight, &write );
		WriteConsoleOutputCharacter( _active_screen_buffer, &TCHAR_INFO(w.border[text_right]), 1, posRight, &write );
	}
}

xgc_void draw_text( window_t window )
{
	text_window& w = _text_window[window];
	if( w.text == INVALID_BUFFER_INDEX )
		return;

	text_buffer& t = _text_buffer[w.text];

	COORD BufferSize = { t.w, t.h };
	COORD BufferCoord = { w.sx, w.sy };
	SMALL_RECT WriteRect;
	if( w.border )
	{
		WriteRect.Left		= w.x + 1;
		WriteRect.Top		= w.y + 1; 
		WriteRect.Right		= w.x + w.cols - 1;
		WriteRect.Bottom	= w.y + w.rows - 1;
	}
	else
	{
		WriteRect.Left		= w.x;
		WriteRect.Top		= w.y; 
		WriteRect.Right		= w.x + w.cols;
		WriteRect.Bottom	= w.y + w.rows;
	}
	WriteConsoleOutput( _active_screen_buffer, t.buffer, BufferSize, BufferCoord, &WriteRect );
}

static __declspec( thread ) char buffer[64*1024];

xgc_int32 wprintf_ascii( window_t window, xgc_lpcstr fmt, ... )
{
	text_window& w = _text_window[window];

	if( w.text == INVALID_BUFFER_INDEX )
		return -1;

	text_buffer& t = _text_buffer[w.text];

	va_list ap;
	va_start(ap,fmt);

	int len = _vsnprintf( buffer, sizeof(buffer), fmt, ap );
	va_end(ap);

	if( len > 0 )
	{
		// format buffer
		xgc_lpstr current = buffer;
		xgc_uint16 position = w.cursor;

		while( *current )
		{
			switch( *current )
			{
			case '\n':
				{
					xgc_uint16 distance = t.w - (position + 1) % t.w;
					xgc_lpstr end = buffer + len;
					len += distance;
					while( end != current )
					{
						*(end + distance) = *end;
						--end;
					}

					while( current <= end + distance )
					{
						*current = ' ';
						++position;
						++current;
					}
				}
				break;
			case '\r':
				{
					xgc_uint16 fix = position % t.w;
					position -= fix;
					current -= fix;
					for( xgc_lpstr p = current; p + fix + 1 <= buffer + len; ++p )
					{
						*p = *(p + fix + 1);
					}
					len -= fix;
				}
				break;
			default:
				++position;
				++current;
				break;
			}
		}

		xgc_uint16 scroll = (w.cursor + len) > t.w * t.h ? ((w.cursor + len) - t.w * t.h) / t.w : 0;
		if( scroll )
		{
			scroll_buffer( t, scroll );
			w.cursor -= scroll * t.w;
		}

		if( (w.cursor + len) / t.w > w.rows )
			w.sy = (w.cursor + len) / t.w - w.rows;

		current = buffer;
		while( *current && w.cursor < t.w * t.h )
		{
			t.buffer[w.cursor].Attributes = g.current;
			t.buffer[w.cursor].Char.AsciiChar = *current;
			++w.cursor;
			++current;
		}

		draw_text( window );
	}

	return len;
}